package com.cskaoyan.javase.object._4hashcode;

/**
 * hashCode方法,今天我们了解两个问题就够了:
 *      1.hashCode方法的作用是什么?
 *      2.hashCode方法为什么要和equals方法一起重写?
 *
 * hashCode()方法的方法声明：
 * public native int hashCode();
 * 很显然，它是一个本地方法，这个方法也没有任何参数，返回值是一个int类型整数。
 *
 * 方法的作用:
 *      1,返回该对象的哈希码值。
 *      2,支持此方法是为了提高哈希表（例如 java.util.Hashtable 提供的哈希表）的性能。
 *
 * 1.哈希(映射)的概念
 * 哈希映射也是一种特殊的映射，要求：
 *      1.集合A必须是一个无限大小，具有无穷多元素的集合。
 *      2.集合B必须是一个元素有限的集合。
 * 化无限为有限，这就是哈希映射。
 *
 * 2.哈希函数,哈希算法,哈希算法.
 *      在哈希映射当中，集合A和B之间的对应关系f，就是一种映射的规则
 *      称之为哈希函数、哈希方法或者哈希算法等。
 *
 * 3.哈希值,哈希码值
 * 通过哈希算法，求得的集合B中的元素，求称之为哈希值。
 *
 * 4.哈希冲突
 *      化无限为有限，这本身就是不现实的，更不可能强求映射是完全一一对应的。
 *      所以在哈希映射中，出现多对一是相当正常的，称之为"哈希冲突"。
 *
 * 以上,来理解一下Java中的哈希映射:
 *      集合A是某个类的全体对象(理论上来说,这是一个无限的集合)
 *      集合B是int的全体数值(是有限集合)
 *      哈希方法就是Java中Object类当中的hashCode方法的实现
 *      hashCode方法的返回值,就是该对象的哈希值
 *      比如:
 *      对于String类型的哈希映射,规定: 该字符串的长度是几,它的哈希值就是几
 *      这就是一个哈希映射,绝不可能出现一对多,当然多对一是很正常的,这是哈希冲突
 *      "123"字符串对象,它的哈希值就是3
 *      "1234"字符串对象,它的哈希值就是4
 *      "abc"字符串对象,它的哈希值就是3
 *      这种算法的情况下,哈希冲突很明显是很多的,多对一很多.
 *
 * 5.什么是哈希表?
 * 键值对在Java当中,它也是一个对象
 * 哈希表可以看成是键值对对象的数组,哈希表的本质是数组
 * 键值对对象中,有key,有value,其中key是唯一性标识,value是存储的取值
 * 哈希表本质是数组,它也是有下标的,所以它继承了数组使用下标的随机访问,访问的效率高
 * 一个键值对对象,如何决定在哈希表中的存储下标呢?
 * 由key对象的哈希值决定的,通过计算key对象的哈希值,然后取模数组的长度,得出该键值对对象的存储下标
 * 在这个过程中,由于哈希冲突的存在,多个key对象的哈希值完全可能是相同的
 * 那么这样,通过计算这些键值对对象在哈希表中的存储位置就是一直的
 * 这时这些键值对的存储一般采取"拉链法",采用链表的形式将这些键值对对象挂起来
 *
 * 在哈希表的存储过程中,被挂起来的键值对肯定是越少越好
 *  挂起来的元素需要通过链表来访问,它们就不是随机访问了,访问的效率很低(相比较于数组)
 * -->
 * 需要减少哈希冲突
 * -->
 * 设计更好的哈希算法,更优秀的哈希算法,来尽量减少哈希冲突
 *
 * 现在,只剩下最后一个问题了:
 * hashCode方法为什么要和equals方法一起重写呢? 这两个方法到底有什么关系呢?
 * 哈希表中键值对对象的存储
 * key是唯一性标识,哈希表中不可能存储两个相同的key,那么如何判断两个key是相同的呢?
 * 如何判断两个key对象相等呢?
 * 用key类型的equals方法
 * 如果该equals方法比较两个key对象,返回true,说明:
 *      a.说明这两个可以是不能同时存在的,新插入的key键值对会覆盖掉原先的键值对
 *          这时新插入的键值对必须和原先的老键值对存储位置一样
 *          --> 这时key对象计算的哈希值也必须和老key对象的哈希值一样
 *          说白了:
 *          当a.equals(b)返回true时,那么a对象的哈希值和b对象的哈希值必须一样
 *     b.既然两个key是相同的,那么它们就是同一个key
 *          说明它们就是哈希映射中集合A当中的同一个元素
 *          映射允许出现多对一,但绝不可能一对多
 *          所以这两个key应该返回同一个哈希值,唯一的映射到集合B中的元素
 *
 *  总之,当equals方法认定对象相等返回true时,这两个对象的哈希值必须一样.
 *
 * 如果该equals方法比较两个key对象,返回false,说明:
 *      这两个key是集合A当中的不同对象,这时不强制要求它们的哈希值不同
 *      因为哈希冲突是存在的,不同对象映射到集合B上的同一个元素是正常的
 *      但是这种哈希冲突,应该尽量避免
 *      如果我们能够尽量为不同的对象,生成不同的哈希值,尽量避免哈希冲突
 *      那么哈希表的性能会更高
 *
 * 总之,当equals方法认定对象不相等返回false时,不强制它们的哈希值必须不同,但是我们应该认识到
 *      为不同的对象生成不同的哈希值,可以提升哈希表的性能
 *
 * Object类当中hashCode方法的默认实现:
 * 实际上，由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。
 * （这一般是通过将该对象的内部地址转换成一个int整数来实现的，但是 Java 编程语言不需要这种实现技巧。）
 * 所以默认实现是计算对象的地址,得出一个十进制地址值.
 *
 * 以上,关于hashCode方法和equals方法的重写总结:
 *      1.如果这两个方法都不重写的话,equals比较的是地址,hashCode也是根据地址计算
 *          那么这样就能够保证equals方法返回true,哈希值一样.
 *      2.如果仅单独重写某个方法的话,以上原则就无法满足了,所以这两个方法要重写必须一起重写
 *          不能单独重写某一个方法.
 *      3.如果同时重写两个方法,那么必须按照同一个规则重写
 *          比如equals方法按照age的取值判断对象相等
 *          那么hashCode方法也必须按照age来计算哈希值
 *
 * 注意事项:
 *  1.一旦重写hashCode方法后，默认的toString方法就不会再打印地址值了。
 *      这是因为toString方法，默认调用的是本地方法的hashCode方法获取地址值，重写后不再是地址值了。
 * 2.如果类中有引用数据类型成员变量，那么就继续调用它的hashCode方法，也就需要重写它的hashCode方法。
 *
 * @since 15:08
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 700);
        Student s2 = new Student(18, 700);
        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        Student s3 = s1;
        System.out.println(s1.equals(s3));
        System.out.println(s1.hashCode());
        System.out.println(s3.hashCode());


        System.out.println(s1);
        System.out.println(s2);
    }
}

class Student {
    int age;
    double score;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }
}
